﻿/*!
* jQuery.bindTools JavaScript Library v2.0
* http://code.google.com/p/ornamentframework
*
* Copyright 2010
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://code.google.com/p/ornamentframework/wiki/LICENSE
*
* Date: 2010-6-21
*/
/// <reference path="jquery.min-vsdoc.js" />
// convertion
(function($) {
    $.fn.bindTool = function(options) {
        var result = $(this),
        args = arguments, optionsKey = "bindTool";
        result.each(function() {
            var target = $(this);
            if (typeof options == "string") {
                var commandArgs = [target.data(optionsKey)].concat([].slice.call(args, 1));
                result = bindTool[options].apply($(this), commandArgs);

            } else {
                var opts = $.extend({}, $.fn.bindTool.defaults, options);
                if (opts.template) {
                    $(opts.template).hide().data("idx", 0);
                }
                var tmpTarget = [opts.template];
                for (var key in options) {
                    var temp = options[key].template;
                    if (temp != undefined && temp.jquery) {
                        temp.data("idx", 0);
                        tmpTarget.push(temp);
                    }
                }
                result.data('btTmp', tmpTarget);
                bindWayUtil.ini(opts);
                target.data(optionsKey, opts);
            }
        });
        return result;
    };

    $.fn.bindTool.defaults = {
        bindWay: false, //[ {member: "对象的成员，支持property.property的方式", to: 绑定控件的方式
        //，可以是html id，或者jquery对象，或者"#id.attrbuteName", from: 返回的数据对象}]
        template: false, //bind的对象是否为template，默认为false
        bindToContent: function(templateIns) { $(templateIns).appendTo(this); }, //template完成数据绑定,加入content的方法;       
        show: function(beArea) {//当template,bind已经完成，准备显示的时候
            $(beArea).show();
        },
        onBind: false, //bind json之前
        onBound: false, //bind 之后发生
        "get": false //Get remote json object by Ajax,please refere jquery.options.                
    };

    $.convert = {
        toObj: function(type, strValue) { // string to Object;
            if (!type) type = "string";
            var ary = type.split("-");
            if (ary.length > 1) {
                return strValue;
            }
            return this[type](strValue);
        },
        toStr: function(objValue) { // obj to str;
            var type = $.convert.getType(objValue).split("-");
            switch (type[0]) {
                case "date":
                    return $.convert.options.dateFormat(this.date(objValue));
                case "array":
                    return objValue;
                default:
                    return objValue;
            }
        },
        getType: function(obj) {
            function isDate() {
                if (obj instanceof Date)
                    return true;
                var result = false;
                $.each($.convert.options.dateExp, function() {
                    if (this.pattern.test(obj)) {
                        result = true;
                        return false;
                    }
                });
                return result;
            }
            if (obj instanceof Array) {
                return "array-" + $.convert.getType(obj[0]);
            }
            return isDate() ? "date" : typeof obj;
        },
        'string': function(strValue) { return strValue; },
        'number': function(strValue) {
            return parseFloat(strValue);
        },
        'boolean': function(strValue) {
            return $.convert.options.boolExp[strValue];

        },
        'date': function(obj) {
            var type = typeof obj;
            if (type == "string") {
                var exps = $.convert.options.dateExp;
                for (var i = 0; i < exps.length; i++) {
                    var exp = exps[i];
                    if (exp.pattern.test(obj)) {
                        return exp.toObj(obj);
                    }
                }
                var result = Date.parse(obj);
                return isNaN(result) ? false : new Date(result);
            }
            if (type == "object" && obj instanceof Date) {
                return obj;
            }
            return false;
        },
        'object': function(obj) {
            return obj;
        }


    };

    $.convert.options = {
        boolExp: { "0": false, "1": true, "true": true, "false": false },
        dateFormat: function(dateVal) { //在Html呈现的时候，使用的格式 默认是2009-10-10
            return dateVal.getFullYear() + "-" + (dateVal.getMonth() + 1) + "-" + dateVal.getDate();
        },
        dateExp: [
            {
                pattern: /(^\/Date\(\d*\)\/$)/, //asp.net 日期格式
                toObj: function(v) { return eval("new " + v.substr(1, v.length - 2)); }
            },
            {
                pattern: /\d{4}-\d{1,2}-\d{1,2}/, //匹配2009-1-1 
                toObj: function(val) {
                    var stAr = val.split('-');
                    return new Date(parseInt(stAr[0]), (parseInt(stAr[1]) - 1), parseInt(stAr[2]));
                }
            },
        ]
    };

    var accessor = {
        "get": function(target, attr) {
            target = $(target);
            var self = this;
            if (attr) {
                return self["attr"](target, attr);
            }
            var func = self[target[0].nodeName];
            return (func ? func : self["text"])(target);
        },
        "set": function(target, data, attr) {
            target = $(target);
            var self = this;
            if (attr) {
                return self["attr"](target, attr, data);
            }
            return target.each(function() {
                var func = self[target[0].nodeName];
                (func ? func : self["text"])(target, data);
            });
        },
        INPUT: function(target, data) {
            var getData = data === undefined;
            switch (target.attr("type")) {
                case "radio":
                    var val = null;
                    target.each(function() {
                        var ctrl = $(this);
                        if (getData && ctrl.attr("checked")) {
                            val = ctrl.val();
                            return false;
                        }
                        else if (ctrl.val() == data) {
                            ctrl.attr("checked", "checked");
                            return false;
                        }
                    });
                    return val;
                case "checkbox":
                    if (getData) {
                        return target[0].checked;
                    }
                    target.attr("checked", function() { return data ? "checked" : "" });
                    break;
                default:
                    return target.val(data);
            }
        },
        SELECT: function(target, data) {
            if (data === undefined) {
                return target.val();
            }
            else {
                target.children().removeAttr("selected");
                var v, duplicateValue = [];
                if ($.isArray(data)) {
                    v = $(data);
                }
                else {
                    v = [];
                    v.push(data);
                }
                $(v).each(function() {
                    var val = typeof (data) != "object" ? this.toString() : this;
                    defaults = {
                        name: val,
                        value: val,
                        selected: false
                    },
                    data = $.extend({}, defaults, val);
                    if (data.value != "") {
                        if ($("[value='" + data.value + "']", target).size() == 0) {
                            target.append("<option value='" + data.value + "'>" + data.name + "</option>");
                        }
                        else {
                            data.selected = true;
                        }

                        if (data.selected && $.inArray(data.value, duplicateValue) == -1) {
                            duplicateValue.push(data.value);
                        }
                    }
                });
                target.val(duplicateValue);
            }
        },
        TEXTAREA: function(target, data) {
            return target.val(data);
        },
        text: function(target, data) {
            return target.text(data);
        },
        attr: function(target, attr, data) {
            return target.attr(attr, data);
        }
    }



    var bindWayUtil = {
        ini: function(options) {
            var bindWays = options.bindWay,
                self = this,
                bindWayDefaults = {
                    member: false,
                    type: "string",
                    to: null, //{selector,attr}
                    from: null, //{selector,attr}
                    fromFunc: function(jObj, bindWay, accessorOpts) {

                        //通过jsonExp表达式从jsonObject设定value中储存值
                        var setObjVal = function(jsonExp, value) {
                            var aryKeys = jsonExp.split("."),
                    currentObject = jObj;
                            var objKeys = aryKeys.slice(0, aryKeys.length - 2),
                    propertyName = aryKeys[aryKeys.length - 1];

                            $.each(objKeys, function(i) {
                                var objKey = this;
                                if (currentObject[objKey] == undefined) {
                                    currentObject[objKey] = {};
                                }
                                currentObject = currentObject[objKey];
                            });
                            currentObject[propertyName] = value;
                        };
                        // this poiner is the target includes value includes target control            
                        var v = accessor.get(this, accessorOpts.attr);
                        setObjVal(bindWay.member, $.convert.toObj(bindWay.type, v));
                    },
                    toFunc: function(data, jObject, target, accessorOpts) {// this poiner is the Area includes target control
                        accessor.set(target, $.convert.toStr(data), accessorOpts.attr);
                    },
                    onBind: false,
                    /*function(value,jsonObject){
                    this pointer is document.
                    if return a new value, plugin will use this new value to bind to target.
                    }*/
                    onBound: false, //function(value,jsonObject){}
                    beforeBind: false,
                    afterBound: false
                }
            $(bindWays).each(function(i) {
                options.bindWay[i] = $.extend({}, bindWayDefaults, this);

                /*bindway  has 3 proeprty , member ,to,from,
                1) member,means, member=member,to=member.replace(/\./g,"_"),to=from
                2) a. "to" value which is string, means only set value to this.
                b. bind to is function ,means use function set value.
                3) a. from is function,means get value from this "from"
                b. from value(string),means read proeprty.*/
                var bindWay = options.bindWay[i];
                self.initWays(bindWay, true);
                self.initWays(bindWay, false);
            });
        },
        initWays: function(bindWay, bInitTo) {
            var func = bInitTo ? "toFunc" : "fromFunc";
            var biandWaySetting = bInitTo ? "to" : "from";
            var bindObj = {
                selector: null,
                attr: null
            };
            var defaultSelector = bindWay.member ? "#" + bindWay.member.replace(/\./g, "_") : false;

            //init
            bindObj.selector = bindWay[biandWaySetting];
            bindWay[biandWaySetting] = $.extend({}, bindObj, bindWay[biandWaySetting]);

            var selector = bindWay[biandWaySetting].selector;
            if (selector === false) {
                bindWay[func] = false;
                bindWay[biandWaySetting] = false;
            }
            else if ($.isFunction(selector)) {
                bindWay[func] = selector;
                bindWay[biandWaySetting] = true;
            }
            else if (selector === null) {
                if (bInitTo) {
                    bindWay[biandWaySetting].selector = defaultSelector;
                }
                else {
                    bindWay[biandWaySetting] = $.isFunction(bindWay.to) ? false : bindWay.to;
                }
            }
        },

        findCtrl: function(area, selector, childPrefix) {
            if (selector.indexOf("#") == 0) {
                selector = "#" + childPrefix + selector.substr(1);
            }
            var a = $(selector, area);
            if (a.length == 0) {
                alert("Not found target use " + selector + " and " + childPrefix);
            }
            return a;
        },
        TobindWay: function(jObj, ary, parentKey) {
            if (ary == undefined) {
                ary = [];
            }
            for (var key in jObj) {
                var newKey = parentKey == undefined ? key : parentKey + "." + key;
                var v = jObj[key];
                if (typeof v == "object" && !$.isArray(v) && !(v instanceof Date) && v != null) {
                    this.TobindWay(v, ary, newKey);
                }
                else {
                    ary.push({ member: newKey });
                }
            }
            return ary;
        }
    };

    var fireEvent = function(thisPointer, fun) {
        return $.isFunction(fun) ? fun.apply(thisPointer, [].slice.call(arguments, 2)) : undefined;
    };
    //Bind To opts.Template target, and this is Tempalte.
    //this pinter is template model.
    var tmpBind = function(jObject, opts) {
        var tmp = this,
            idx, //index of template instance.
            templateId, //template'sid
            newPrefix,
            searchArea;
        idx = tmp.data("idx");
        tmp.data("idx", ++idx);
        templateId = tmp.attr("id");
        newPrefix = templateId + "_" + idx;
        searchArea = tmp.clone().attr("id", newPrefix).data("template", templateId);
        return tagBind.call(searchArea, jObject, opts, "", newPrefix + "_");

    }
    var gv = function(o, p) { return eval("o." + p); };
    //Bind to panel. not for tempalte
    // this poiner is the area to be bound.
    var tagBind = function(jObject, opts, childTargetPrefix, newPrefix) {

        var searchArea = this;
        $.each(opts.bindWay, function() {
            if (this.toFunc) {
                var bindTarget = false,
				    value = this.member ? gv(jObject, this.member) : undefined;
                if (this.to !== true) {
                    bindTarget = bindWayUtil.findCtrl(searchArea, this.to.selector, childTargetPrefix);
                    this.type = $.convert.getType(value);
                    if (bindTarget.length == 0)
                        bindTarget = false;
                }
                var tmpValue = fireEvent(bindTarget ? bindTarget : document, this.onBind, value, jObject);
                if (tmpValue === false) {
                    return searchArea;
                }
                if (tmpValue === "false") {// special.
                    tmpValue = false;
                }
                if (tmpValue != undefined) {
                    value = tmpValue;
                }
                this.toFunc.call(searchArea, value, jObject, bindTarget, this.to);
                fireEvent(bindTarget ? bindTarget : document, this.onBound, value, jObject);
                if (newPrefix) {
                    bindTarget.attr("id", newPrefix + bindTarget.attr("id"));
                }
            }
        });
        return searchArea;
    }
    //templateNameOrTemplateIns on templateName to find area will be bound.
    var getRealArea = function(searchRang, opts, templateNameOrTemplateIns) {
        var returnArray = false;
        if (opts.template) {
            if (templateNameOrTemplateIns && templateNameOrTemplateIns.jquery) {
                searchRang = templateNameOrTemplateIns;
                returnArray = (templateNameOrTemplateIns).jquery && templateNameOrTemplateIns.length > 1;
            }
            else {
                var tmpModel = templateNameOrTemplateIns ? $.makeArray(opts[templateNameOrTemplateIns].template) : $(searchRang).data("btTmp"),
                    selector = [];
                $(tmpModel).each(function() {
                    var idx = $(this).data("idx");
                    if (idx == 0) { return true; }
                    for (var i = 1; i <= idx; i++) {
                        selector.push("#" + $(this).attr("id") + "_" + i);
                    }
                });
                searchRang = $(selector.join(","), searchRang);
                returnArray = true;
            }
        }
        return { area: searchRang, aryResult: returnArray };
    };
    //bindTool's this pointer always is $(html);
    var bindTool = {
        bind: function(opts, jsonObject, templateIns) {
            var target = this;
            if (opts.get) {
                opts.get.data = jsonObject;
                opts.get.success = function(data) {
                    jsonObject = data;
                    bindTool._bind.call(target, opts, jsonObject, templateIns);
                };
                $.ajax(opts.get);
                return target;
            }
            else {
                return bindTool._bind.call(target, opts, jsonObject, templateIns);
            }
        },
        //绑定数据到 opts 指定的控件上面
        _bind: function(opts, jsonObject, templateIns) {
            var bindTarget = $(this),
                isTmpIns = templateIns != undefined;
            fireEvent(bindTarget, opts.beforeBind, jsonObject);

            if (!opts.bindWay) {
                opts.bindWay = bindWayUtil.TobindWay($.isArray(jsonObject) ? jsonObject[0] : jsonObject);
                bindWayUtil.ini(opts);
            }
            if (isTmpIns) {
                bindTarget = templateIns;
            }
            var result = false;

            var onBindResult = fireEvent(bindTarget, opts.onBind, jsonObject);

            if (typeof onBindResult == 'object') {
                jsonObject = onBindResult;
            } else if (onBindResult === false) {
                return result;
            }

            var newTemplate = opts.template && !isTmpIns;
            result = [];
            $(jsonObject).each(function() {
                var obj = fireEvent(bindTarget, opts.onBind, this);
                if (obj === false) {
                    return true;
                }
                else if (obj === undefined) {
                    obj = this;
                }
                if (newTemplate) {
                    var temp = tmpBind.call(opts.template, obj, opts);
                    if (temp) {
                        fireEvent(bindTarget, opts.bindToContent, temp);
                        result.push("#" + temp.attr("id"));
                    }
                    fireEvent(bindTarget, opts.onBound, obj, temp);
                }
                else {
                    var prefix = bindTarget.data("template") !== undefined ? bindTarget.attr("id") + "_" : "";
                    result = $(tagBind.call(bindTarget, obj, opts, prefix));
                    fireEvent(bindTarget, opts.onBound, obj, result);
                }

            });
            if (newTemplate) {
                result = $(result.join(","));
                fireEvent(bindTarget, opts.show, result);
            }
            fireEvent(this, opts.afterBound, jsonObject);
            return result;
        },
        collect: function(opts, templateNameOrTemplateIns) {
            var area = getRealArea(this, opts, templateNameOrTemplateIns);
            var result = [];
            $.each(area.area, function() {
                var jsonObject = {},
                 inSearchRang = $(this),
                 prefix = "";
                if (inSearchRang.data("template") != undefined) {
                    var prefixAry = inSearchRang.attr("id").split("_");
                    prefix = prefixAry[0] + "_" + prefixAry[1] + "_";
                }

                $.each(opts.bindWay, function() {
                    var bindWay = this;
                    if (bindWay.fromFunc) {
                        var target = bindWay.from !== true ? bindWayUtil.findCtrl(inSearchRang, bindWay.from.selector, prefix) : document;
                        //var membername = bindWay.member ? bindWay.member : undefined;
                        fireEvent(target, this.fromFunc, jsonObject, bindWay, this.from);
                    }
                });
                result.push(jsonObject);
            });
            /*
            * 返回当个对象
            1)没有使用Template功能
            2)使用Template功能，但是只收集一个 template 实例的对象的时候。
            * 返回多个数组
            1) 使用Template功能，收集所有template实例数据的时候。
            2) 指定收集某个区间的template实例。并且大于一个。
            */
            fireEvent(this, arguments[arguments.length], result);
            return area.aryResult ? result : result[0];
        },
        "switch": function(opts, switchTemplateName, switchArea) {

            var targetOpts, targetTemp;
            if (switchTemplateName == "template") {
                targetOpts = opts;
                targetTemp = opts.template;
            }
            else {
                targetOpts = $.extend({
                    bindWay: opts.bindWay,
                    show: opts.show
                }, this[switchTemplateName]);
                targetTemp = opts[switchTemplateName].template;
            }

            var jsonObj = $(this).bindTool('collect', switchArea),
                bindTemp = tmpBind.call(targetTemp, jsonObj, targetOpts);

            if (bindTemp) {
                switchArea.replaceWith(bindTemp);
                opts.show(bindTemp);
            }
        },
        clear: function(opts, templateNameOrTemplateIns) {
            var r = getRealArea(this, opts, templateNameOrTemplateIns),
                searchArea = r.area;
            $.each(opts.bindWay, function() {
                if (this.toFunc) {
                    var prefix = "";
                    if (searchArea.data("template") != undefined) {
                        var prefixAry = searchArea.attr("id").split("_");
                        prefix = prefixAry[0] + "_" + prefixAry[1] + "_";
                    }

                    var bindTarget = false;

                    if (this.to !== true) {
                        bindTarget = bindWayUtil.findCtrl(searchArea, this.to.selector, prefix);
                        if (bindTarget.length == 0)
                            bindTarget = false;
                    }
                    this.toFunc.call(searchArea, "", null, bindTarget, this.to);
                }
            });
            return searchArea;
        }
    };


})($);

